package com.element.ui.list;

import com.element.util.CompareUtil;

import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

public class CheckBoxListSelectionModel extends DefaultListSelectionModel implements ListDataListener {
	private ListModel _model;
	private boolean _allEntryConsidered = true;
	private int _allEntryIndex = -1;
	private boolean _allEntryIndexSet = false;

	public CheckBoxListSelectionModel() {
		setSelectionMode(MULTIPLE_INTERVAL_SELECTION);
	}

	public CheckBoxListSelectionModel(ListModel model) {
		_model = model;
		if (isAllEntryConsidered()) {
			_allEntryIndex = findAllEntryIndex();
			_model.addListDataListener(this);
		}
		setSelectionMode(MULTIPLE_INTERVAL_SELECTION);
	}

	/**
	 * Gets the index of the "all" entry in the CheckBoxList.
	 *
	 * @return the index of the "all" entry. -1 if no "all" entry exists.
	 * @see #setAllEntryIndex(int)
	 * @since 3.3.5
	 */
	public int getAllEntryIndex() {
		return _allEntryIndex;
	}

	/**
	 * Sets the index of the "all" entry in the CheckBoxList.
	 * <p/>
	 * If the CheckBoxList has an "all" entry, check that entry on/off will check/uncheck all other entries. Uncheck any
	 * other entry will uncheck the "all" entry.
	 *
	 * @param allEntryIndex the index of the "all" entry.
	 */
	public void setAllEntryIndex(int allEntryIndex) {
		_allEntryIndex = allEntryIndex;
		_allEntryIndexSet = _allEntryIndex != -1;
	}

	private int findAllEntryIndex() {
		if (getModel() == null) {
			return -1;
		}
		int size = getModel().getSize();
		for (int i = 0; i < size; i++) {
			if (CompareUtil.equals(getModel().getElementAt(i), CheckBoxList.ALL_ENTRY)) {
				return i;
			}
		}
		return -1;
	}

	public ListModel getModel() {
		return _model;
	}

	public void setModel(ListModel model) {
		int oldLength = 0;
		int newLength = 0;
		if (_model != null) {
			oldLength = _model.getSize();
			_model.removeListDataListener(this);
		}
		_model = model;
		if (_model != null) {
			newLength = _model.getSize();
			_model.removeListDataListener(this);
			if (isAllEntryConsidered()) {
				_model.addListDataListener(this);
				_allEntryIndex = findAllEntryIndex();
			} else if (!_allEntryIndexSet) {
				_allEntryIndex = -1;
			}
		}
		if (oldLength > newLength) {
			removeIndexInterval(newLength, oldLength);
		}
	}

	@Override
	public void intervalAdded(ListDataEvent e) {
		if (isAllEntryConsidered()) {
			_allEntryIndex = findAllEntryIndex();
			updateAllEntryIf();
		}
	}

	@Override
	public void intervalRemoved(ListDataEvent e) {
		if (isAllEntryConsidered()) {
			_allEntryIndex = findAllEntryIndex();
			updateAllEntryIf();
		}
	}

	@Override
	public void contentsChanged(ListDataEvent e) {
		if (isAllEntryConsidered()) {
			_allEntryIndex = findAllEntryIndex();
			updateAllEntryIf();
		}
	}

	protected void updateAllEntryIf() {
		if (_allEntryIndex != -1) {
			if (isSelectedIndex(_allEntryIndex))
				unselectAllIf();
			else
				selectAllIf();
		}
	}

	/**
	 * Overrides so that inserting a row will not be selected automatically if the row after it is selected.
	 *
	 * @param index  the index where the rows will be inserted.
	 * @param length the number of the rows that will be inserted.
	 * @param before it's before or after the index.
	 */
	@Override
	public void insertIndexInterval(int index, int length, boolean before) {
		if (index < 0) {
			return;
		}
		if (before) {
			boolean old = isSelectedIndex(index);
			boolean adjusting = getValueIsAdjusting();
			super.setValueIsAdjusting(true);
			try {
				if (old) {
					removeSelectionInterval(index, index);
				}
				super.insertIndexInterval(index, length, before);
				if (old) {
					addSelectionInterval(index + length, index + length);
				}
			} finally {
				super.setValueIsAdjusting(adjusting);
			}
		} else {
			super.insertIndexInterval(index, length, before);
		}
	}

	/**
	 * Gets the flag indicating if this CheckBoxListSelectionModel should consider the CheckBoxList.ALL item if there is
	 * one.
	 *
	 * @return true if need check. Otherwise false.
	 * @see #setAllEntryConsidered(boolean)
	 * @since 3.3.3
	 */
	public boolean isAllEntryConsidered() {
		return _allEntryConsidered;
	}

	/**
	 * Sets the flag indicating if this CheckBoxListSelectionModel should consider the CheckBoxList.ALL item if there is
	 * one.
	 * <p/>
	 * By default, the flag is true. If you want to improve the performance and don't have "all" entry, or if you do
	 * have an entry similar to "all" entry but want to treat it as a normal entry, please set it to false.
	 *
	 * @param allEntryConsidered the flag
	 * @since 3.3.3
	 */
	public void setAllEntryConsidered(boolean allEntryConsidered) {
		_allEntryConsidered = allEntryConsidered;
		if (_model != null) {
			_model.removeListDataListener(this);
		}
		if (isAllEntryConsidered()) {
			_allEntryIndex = findAllEntryIndex();
			if (_model != null) {
				_model.addListDataListener(this);
			}
		} else {
			_allEntryIndex = -1;
		}
	}

	@Override
	public boolean isSelectedIndex(int index) {
		boolean selected = super.isSelectedIndex(index);
		return selected || (getAllEntryIndex() >= 0 && super.isSelectedIndex(getAllEntryIndex()));
	}

	private boolean selectAll(int index0, int index1) {
		if (getAllEntryIndex() < 0) {
			return false;
		}
		if ((index0 == 0 && index1 == getModel().getSize() - 1) || (index1 == 0 && index0 == getModel().getSize() - 1)) {
			return false;
		}
		if ((index0 >= getAllEntryIndex() && index1 <= getAllEntryIndex()) || (index1 >= getAllEntryIndex() && index0 <= getAllEntryIndex())) {
			setSelectionInterval(0, getModel().getSize() - 1);
			return true;
		} else {
			return false;
		}
	}

	private boolean unselectAll(int index0, int index1) {
		if (getAllEntryIndex() < 0) {
			return false;
		}
		if (index0 == getAllEntryIndex() || index1 == getAllEntryIndex()) {
			clearSelection();
			return true;
		} else {
			return false;
		}
	}

	private void selectAllIf() {
		if (getAllEntryIndex() < 0) {
			return;
		}
		for (int i = getModel().getSize() - 1; i >= 0; i--) {
			if (i != getAllEntryIndex() && !super.isSelectedIndex(i)) {
				return;
			}
		}
		super.addSelectionInterval(getAllEntryIndex(), getAllEntryIndex());
	}

	private void unselectAllIf() {
		if (getAllEntryIndex() < 0) {
			return;
		}
		for (int i = getModel().getSize() - 1; i >= 0; i--) {
			if (i != getAllEntryIndex() && !super.isSelectedIndex(i)) {
				super.removeSelectionInterval(getAllEntryIndex(), getAllEntryIndex());
				return;
			}
		}
	}

	@Override
	public void setSelectionInterval(int index0, int index1) {
		boolean adjusting = getValueIsAdjusting();
		setValueIsAdjusting(true);
		try {
			if (!selectAll(index0, index1)) {
				super.setSelectionInterval(index0, index1);
				selectAllIf();
			}
		} finally {
			setValueIsAdjusting(adjusting);
		}
	}

	@Override
	public int getMinSelectionIndex() {
		int index = super.getMinSelectionIndex();
		if (getAllEntryIndex() < 0) {
			return index;
		}
		if (super.isSelectedIndex(getAllEntryIndex()) && getAllEntryIndex() == 0) {
			return 1;
		}
		return index;
	}

/*
    @Override
    public int getMaxSelectionIndex() {
        if (super.isSelectedIndex(0)) {
            return 0;
        }
        return super.getMaxSelectionIndex();
    }
*/

	@Override
	public void addSelectionInterval(int index0, int index1) {
		boolean adjusting = getValueIsAdjusting();
		setValueIsAdjusting(true);
		try {
			if (!selectAll(index0, index1)) {
				super.addSelectionInterval(index0, index1);
				selectAllIf();
			}
		} finally {
			setValueIsAdjusting(adjusting);
		}
	}

	// implements javax.swing.ListSelectionModel

	@Override
	public void removeSelectionInterval(int index0, int index1) {
		boolean adjusting = getValueIsAdjusting();
		setValueIsAdjusting(true);
		try {
			if (!unselectAll(index0, index1)) {
				if (getAllEntryIndex() >= 0) {
					super.removeSelectionInterval(getAllEntryIndex(), getAllEntryIndex());
				}
				super.removeSelectionInterval(index0, index1);
			}
		} finally {
			setValueIsAdjusting(adjusting);
		}
	}
}
